// code modified from project Periodic32bitT0Ints

#include "OS.h"
#include "PLL.h"
#include "tm4c123gh6pm.h"  // modified

// Lab 1
#define NVIC_EN0_INT19          0x00080000  // Interrupt 19 enable

void DisableInterrupts(void); // Disable interrupts
void EnableInterrupts(void);  // Enable interrupts
long StartCritical (void);    // previous I bit, disable interrupts
void EndCritical(long sr);    // restore I bit to previous value
void WaitForInterrupt(void);  // low power mode
void (*PeriodicTask)(void);   // user function

//******** OS_AddPeriodicThread *************** 
// add a background periodic task
// typically this function receives the highest priority
// Inputs: pointer to a void/void background function
//         period given in system time units (12.5ns)
//         priority 0 is the highest, 5 is the lowest
// Outputs: 1 if successful, 0 if this thread can not be added
// used Timer0A
int OS_AddPeriodicThread(void(*task)(void), unsigned long period, unsigned long priority){
	//long sr;
	//sr = StartCritical(); 
	DisableInterrupts();
	
  SYSCTL_RCGC1_R |= SYSCTL_RCGC1_TIMER0; // 0) activate timer0
  PeriodicTask = task;             // user function
  TIMER0_CTL_R &= ~TIMER_CTL_TAEN; // 1) disable timer0A during setup
                                   // 2) configure for 32-bit timer mode
  TIMER0_CFG_R = TIMER_CFG_32_BIT_TIMER;
                                   // 3) configure for periodic mode, default down-count settings
  TIMER0_TAMR_R = TIMER_TAMR_TAMR_PERIOD;
  TIMER0_TAILR_R = period-1;       // 4) reload value
                                   // 5) clear timer0A timeout flag
  TIMER0_ICR_R = TIMER_ICR_TATOCINT;
  TIMER0_IMR_R |= TIMER_IMR_TATOIM;// 6) arm timeout interrupt
  NVIC_PRI4_R = (NVIC_PRI4_R&0x00FFFFFF)|(priority<<29); // 7) priority, modified
  NVIC_EN0_R |= NVIC_EN0_INT19;     // 8) enable interrupt 19 in NVIC, modified
  TIMER0_CTL_R |= TIMER_CTL_TAEN;  // 9) enable timer0A
	
  //EndCritical(sr);	
	//no enable interrupts until startOS
	return 1;
}

// PD0 is used for ISR measurements
//#define PD0                    (*((volatile unsigned long *)0x40007004))
void Timer0A_Handler(void){
//	PD0 = 0x01;
  TIMER0_ICR_R = TIMER_ICR_TATOCINT;   // acknowledge timer0A timeout
  (*PeriodicTask)();                   // execute user task
//	PD0 = 0x00;
}


// to be added functions
void OS_ClearPeriodicTime(void){
	
}

unsigned long OS_ReadPeriodicTime(void){
	int time;
	
	return time;
}

// Lab 1

// Lab 2
// define tcb structure
#define NUMTHREADS 10
#define STACKSIZE 100

struct tcb{
	long *sp;          // point to current TCB
	struct tcb *next;  // point to next TCB
	struct tcb *prev;  // point to previous TCB
	int Id;            // id of thread
	int SlpState;      // sleep state
	int Priority;      // priority
	int BlkState;      // block state
	int InUse;         // TCB in use, modified
	int TCBID;         // TCB ID, used to release resource of TCB, modified
	//long *Stk;          // stack
};
typedef struct tcb tcbType;
tcbType tcbs[NUMTHREADS];
tcbType *RunPt;
long Stacks[NUMTHREADS][STACKSIZE];    // how to use dynamic stack
// define tcb structure

void SetInitialStack(int i){
	tcbs[i].sp = &Stacks[i][STACKSIZE-16];
	Stacks[i][STACKSIZE-1] = 0x01000000;
	Stacks[i][STACKSIZE-3] = 0x14141414;
	Stacks[i][STACKSIZE-4] = 0x12121212;
	Stacks[i][STACKSIZE-5] = 0x03030303;
	Stacks[i][STACKSIZE-6] = 0x02020202;
	Stacks[i][STACKSIZE-7] = 0x01010101;
	Stacks[i][STACKSIZE-8] = 0x00000000;
	Stacks[i][STACKSIZE-9] = 0x11111111;
	Stacks[i][STACKSIZE-10] = 0x10101010;
	Stacks[i][STACKSIZE-11] = 0x09090909;
	Stacks[i][STACKSIZE-12] = 0x08080808;
	Stacks[i][STACKSIZE-13] = 0x07070707;
	Stacks[i][STACKSIZE-14] = 0x06060606;
	Stacks[i][STACKSIZE-15] = 0x05050505;
	Stacks[i][STACKSIZE-16] = 0x04040404;
}

//modified
//******** OS_InitTimer2 ***************
// use Timer2 as global timer
// used to count sleep time
int OS_InitTimer2(void){ volatile unsigned long delay;
	unsigned long period = 80000;  // 12.5ns * 8 = 0.1us, 1ms
	unsigned long sr;
  sr = StartCritical(); 
	
  SYSCTL_RCGC1_R |= SYSCTL_RCGC1_TIMER2; // 0) activate timer2
  delay = SYSCTL_RCGC1_R;                   // allow time to finish activating
  TIMER2_CTL_R &= ~TIMER_CTL_TAEN; // 1) disable timer2A during setup
                                   // 2) configure for 32-bit timer mode
  TIMER2_CFG_R = TIMER_CFG_32_BIT_TIMER;
                                   // 3) configure for periodic mode, default down-count settings
  TIMER2_TAMR_R = TIMER_TAMR_TAMR_PERIOD;
  TIMER2_TAILR_R = period-1;       // 4) reload value
                                   // 5) clear timer2A timeout flag
  TIMER2_ICR_R = TIMER_ICR_TATOCINT;
  TIMER2_IMR_R |= TIMER_IMR_TATOIM;// 6) arm timeout interrupt
  NVIC_PRI5_R = (NVIC_PRI5_R&0x00FFFFFF)|(2<<29); // 7) Timer2A priority 2, modified
  NVIC_EN0_R |= 0x00800000;     // 8) enable interrupt 23 in NVIC
  TIMER2_CTL_R |= TIMER_CTL_TAEN;  // 9) enable timer0A
	
  EndCritical(sr);
	return 1;
}

int TimerCnt = 0;
void Timer2A_Handler(void){
	int i;
	TIMER2_ICR_R = TIMER_ICR_TATOCINT;  // acknowledge Timer2
	for(i=0;i<10;i++){            // check every tcb
		if(tcbs[i].SlpState != 0){  // if SlpState != 0, means sleeping
			tcbs[i].SlpState -= 1;    // SlpState - 1, means passed 0.1us, since interrupts every 0.1us
		}
	}
  TimerCnt++;
}
// modified

int OS_InitTimer3(void);

//******** OS_InitTCB ***************
// used to give TCBID
int OS_InitTCB(void){
	int i;
	for(i=0;i<10;i++){
		tcbs[i].InUse = 0;
		tcbs[i].TCBID = i;
	}
	return 1;
}

//******** OS_InitTCB ***************


//******** OS_AddThread ***************
// add a foregound thread to the scheduler
int NumCreated;     // number of threads counter, need to make global
int LastThread = 0; // which thread is the last one of the LL, modified
int OS_AddThread(void(*task)(void), unsigned long stackSize, unsigned long priority){
	long status;
	int NotInUse;     // used to check which TCB is not in use
		
	status = StartCritical();
	
	for(NotInUse=0;NotInUse<10;NotInUse++){     // find tcb not in use
		if(tcbs[NotInUse].InUse == 0) break;
	}
	
	if(NotInUse == 0){                          // tcbs[0] not in use, assume tcbs[0] thread never sleep/block/die
		tcbs[NotInUse].next = &tcbs[NotInUse];
		tcbs[NotInUse].prev = &tcbs[NotInUse];    // modified
	}
	else{
		tcbs[LastThread].next = &tcbs[NotInUse];
		tcbs[NotInUse].prev = &tcbs[LastThread];  // modified, to be replaced by LastThread
	}
	tcbs[NotInUse].next = &tcbs[0];             // set next pt
	tcbs[NotInUse].Priority = priority;         // set priority
	
	tcbs[NotInUse].InUse = 1;                   // tcb is in use now
	tcbs[NotInUse].BlkState = 0;                // added to test context switch
	tcbs[NotInUse].SlpState = 0;
	tcbs[NotInUse].Id = NumCreated;             // need to change if recycle id
	
	SetInitialStack(NotInUse);  Stacks[NotInUse][STACKSIZE-2] = (long) (task);  // set PC
	LastThread = NotInUse;                      // set the last added thread
	
	EndCritical(status);	
	return 1;
}


// ******** OS_Init ************
// initialize operating system, disable interrupts until OS_Launch
// initialize OS controlled I/O: serial, ADC, systick, LaunchPad I/O and timers 
// on book page 177
// SysTick Timer used in OS_Suspend
void OS_Init(void){
	DisableInterrupts();
	PLL_Init();
	OS_InitTCB();
	OS_InitTimer2();              // initialize Timer2 for sleep
	OS_InitTimer3();              // initialize Timer3 for OS_Time
	NVIC_ST_CTRL_R = 0;           // disable SysTick interrupt during setup
	NVIC_ST_CURRENT_R = 0;        // clears current counter
	NVIC_SYS_PRI3_R = (NVIC_SYS_PRI3_R&0x00FFFFFF)|(6<<29);  // SysTick priority 6
}


//******** OS_Launch *************** 
// start the scheduler, enable interrupts
// Inputs: number of 12.5ns clock cycles for each time slice
//         you may select the units of this parameter
// Outputs: none (does not return)
// use SysTick to count timeslice
// 5 use OS_Suspend for cooperative context switch, will suspend for theTimeSlice time
// or 7 preempt context switch by SysTick handler
void StartOS(void);
void OS_Launch(unsigned long theTimeSlice){
	NVIC_ST_RELOAD_R = theTimeSlice - 1;    // reload value
	NVIC_ST_CTRL_R = 0x00000007;            // enable, core clock and interrupt arm, use this if SysTick to context switch
	//NVIC_ST_CTRL_R = 0x00000005;            // arm timer, core clock, disable interrupt, use this if PendSV to context switch
	//OS_InitTimer2();                        // initialize Timer2 for sleep
	RunPt = &tcbs[0];                       // initialize RunPt
	StartOS();
}


// ******** OS_Suspend ************
// suspend execution of currently running thread
// scheduler will choose another thread to execute
// Can be used to implement cooperative multitasking 
// used SysTick Timer, used PendSV for context switch
// time for suspend is theTimeSlice
void OS_Suspend(void){
  NVIC_ST_CURRENT_R = 0;                   // any write to current clears it
	while((NVIC_ST_CTRL_R&0x00010000)==0);   // wait for count flag
	NVIC_INT_CTRL_R |= 0x10000000;   // trigger PendSV intterrupt for context switch
}


// added for later use
unsigned long LastTCB;    // last TCB in the linked list, when add new thread, connect to this
// use Timer interrupt to count time, may be reload value = 8, so every 8*12.5ns = 0.1us will trigger an interrupt
// in Timer_Handler, search tcbs, decrease sleep counter if thread in a tcb is sleeping
// use PendSV handler for context switch
// runpt = runpt.next
// if new runpt.slp != 0 means this thread is sleeping
// skip the sleeping thread by runpt = runpt.next

//modified, to test sleep and block
void OS_Wake(void){
	tcbs[1].SlpState = 0;
	tcbs[2].BlkState = 0;
}
//modified

// ******** OS_Kill ************
// kill the currently running thread, release its TCB and stack
// input:  none
// output: none
// connect previous tcb to next tcb
// use next to check if last thread in the LL, so that new thread can be added
// if next points to tcb[0], means this is the last thread. assume tcb[0] never die
// release tcb by set used = 0
void OS_Kill(void){
	(*((*RunPt).prev)).next = (*RunPt).next;  // previous thread.next = current thread.next
	(*((*RunPt).next)).prev = (*RunPt).prev;  // next thread.prev = current thread.prev
	(*RunPt).InUse = 0;                       // release tcb block
	if((*RunPt).next == &tcbs[0]){            // if killed thread is the last in LL, its previous will be the last
		LastThread = (*((*RunPt).prev)).TCBID;
	}
	NVIC_INT_CTRL_R |= 0x10000000;   // trigger PendSV intterrupt for context switch
}


// ******** OS_Sleep ************
// place this thread into a dormant state
// input:  number of msec to sleep
// output: none
// in the unit of 1ms
// used Timer2A_Handler to decrease SlpState
void OS_Sleep(unsigned long sleepTime){
	//(*RunPt).SlpState = sleepTime*10000;  // set current thread.SlpState = sleepTime(ms) = sleepTime*10000 (0.1us)
  (*RunPt).SlpState = sleepTime;
	NVIC_INT_CTRL_R |= 0x10000000;   // trigger PendSV intterrupt for context switch, modified
}


// ******** OS_InitSemaphore ************
// initialize semaphore 
// input:  pointer to a semaphore
// output: none
void OS_InitSemaphore(Sema4Type *semaPt, long value){
	(*semaPt).Value = value;
}


// ******** OS_Wait ************
// decrement semaphore 
// Lab2 spinlock
// Lab3 block if less than zero
// input:  pointer to a counting semaphore
// output: none
void OS_Wait(Sema4Type *semaPt){
	unsigned long time;
	//time = 72724*2/91;           // wait for 1ms according to email
	time = 500;           // wait for 
	DisableInterrupts();
	while((*semaPt).Value <= 0){
		EnableInterrupts();
		//OS_Suspend();            // suspend for theTimeSlice time
		while(time){
			time--;
		}
		DisableInterrupts();
	}
	(*semaPt).Value = (*semaPt).Value - 1;
	EnableInterrupts();
}


// ******** OS_Signal ************
// increment semaphore 
// Lab2 spinlock
// Lab3 wakeup blocked thread if appropriate 
// input:  pointer to a counting semaphore
// output: none
void OS_Signal(Sema4Type *semaPt){
	long sr;
	sr = StartCritical();
	(*semaPt).Value = (*semaPt).Value + 1;
	EndCritical(sr);
}



// ******** OS_bWait ************
// Lab2 spinlock, set to 0
// Lab3 block if less than zero
// input:  pointer to a binary semaphore
// output: none
void OS_bWait(Sema4Type *semaPt){
	unsigned long time;
	time = 72724*4/91;           // wait for 2ms according to email
	DisableInterrupts();
	while((*semaPt).Value == 0){    // wait until sema4 released
		EnableInterrupts();
		//OS_Suspend();                 // suspend for theTimeSlice time
		while(time){
			time--;
		}
		DisableInterrupts();
	}
	(*semaPt).Value = 0;            // occupies sema4
	EnableInterrupts();
}


// ******** OS_bSignal ************
// Lab2 spinlock, set to 1
// Lab3 wakeup blocked thread if appropriate 
// input:  pointer to a binary semaphore
// output: none
void OS_bSignal(Sema4Type *semaPt){
	long sr;
	sr = StartCritical();
	(*semaPt).Value = 1;
	EndCritical(sr);
}


//******** OS_AddSW1Task *************** 
// add a background task to run whenever the SW1 (PF4) button is pushed
void (*SW1Task)(void);        // user function
unsigned long static lastPF4;
#define PF4        (*((volatile unsigned long *)0x40025040))
int OS_AddSW1Task(void(*task)(void), unsigned long priority){ 
	unsigned long delay;
//	long sr;
//	sr = StartCritical();
	DisableInterrupts();
	
  SYSCTL_RCGC2_R |= 0x00000020; // (a) activate clock for port F
	SW1Task = task;              // assign task
	delay = SYSCTL_RCGC2_R;
	delay = SYSCTL_RCGC2_R;
  GPIO_PORTF_LOCK_R = 0x4C4F434B;   // 2) unlock PortF PF0  
  GPIO_PORTF_CR_R |= 0x1F;           // allow changes to PF4-0       
  GPIO_PORTF_DIR_R &= ~0x10;    // (c) make PF4 in (built-in button)
  GPIO_PORTF_AFSEL_R &= ~0x10;  //     disable alt funct on PF4
  GPIO_PORTF_DEN_R |= 0x10;     //     enable digital I/O on PF4
  GPIO_PORTF_PCTL_R &= ~0x000F0000; //  configure PF4 as GPIO
  GPIO_PORTF_AMSEL_R &= ~0x10;  //    disable analog functionality on PF4
  GPIO_PORTF_IS_R &= ~0x10;     // (d) PF4 is edge-sensitive
  GPIO_PORTF_IBE_R |= 0x10;    //     PF4 is both edges
  GPIO_PORTF_IEV_R &= ~0x10;    //     PF4 falling edge event
  GPIO_PORTF_ICR_R = 0x10;      // (e) clear flag4
  GPIO_PORTF_IM_R |= 0x10;      // (f) arm interrupt on PF4
	GPIO_PORTF_PUR_R |= 0x10;     // no pullup
	//GPIO_PORTF_PUR_R &= ~0x10;     // no pullup
	lastPF4 = PF4;                // last PF4
	//lastPF4 = 1;                // last PF4, skip 1st interrupt, why start with pending??
  NVIC_PRI7_R = (NVIC_PRI7_R&0xFF00FFFF)|(5<<21); // (g) priority 5
  NVIC_EN0_R = 0x40000000;      // (h) enable interrupt 30 in NVIC
	
//	EndCritical(sr);
// not enable interrupts until startOS
	return 1;
}

void static DebounceTask(void){
	OS_Sleep(2);               // skip 2ms bounce
	lastPF4 = PF4;             // read PF4
	GPIO_PORTF_ICR_R = 0x10;   // clear flag4
	GPIO_PORTF_IM_R |= 0x10;   // arm PF4 interrupt
	OS_Kill();
}
unsigned long SWCnt = 0;
void GPIOPortF_Handler(void){
	//if(lastPF4 == 1){
	if(lastPF4 == 0){
    (*SW1Task)();             // execute user task
	}
	GPIO_PORTF_IM_R &= ~0x10;   // disarm PF4 interrupt
	OS_AddThread(&DebounceTask, 100, 2);
}

//******** end of OS_AddSW1Task *************** 


//void (*SW2Task)(void);        // user function
//unsigned long static lastPF0;
//#define PF0        (*((volatile unsigned long *)0x40025004))
//******** OS_AddSW2Task *************** 
// add a background task to run whenever the SW2 (PF0) button is pushed
int OS_AddSW2Task(void(*task)(void), unsigned long priority){
	
	// to be added
	
	return 1;
}

//******** end of OS_AddSW2Task *************** 


//******** OS_Id *************** 
// returns the thread ID for the currently running thread
// Inputs: none
// Outputs: Thread ID, number greater than zero 
unsigned long OS_Id(void){
	unsigned long OSID;
	OSID = (*RunPt).Id;
	return OSID;
}

//******** OS_InitTimer3 ***************
// use Timer2 as global timer
// used to count sleep time
int OS_InitTimer3(void){ volatile unsigned long delay;
	unsigned long sr;
  sr = StartCritical(); 
	
  SYSCTL_RCGC1_R |= SYSCTL_RCGC1_TIMER3; // 0) activate timer3
  delay = SYSCTL_RCGC1_R;                   // allow time to finish activating
  TIMER3_CTL_R &= ~TIMER_CTL_TAEN; // 1) disable timer3A during setup
                                   // 2) configure for 32-bit timer mode
  TIMER3_CFG_R = TIMER_CFG_32_BIT_TIMER;
                                   // 3) configure for periodic mode, default down-count settings
  TIMER3_TAMR_R = TIMER_TAMR_TAMR_PERIOD;
  TIMER3_TAILR_R = 0xFFFFFFFF;     // 4) reload value
                                   // 5) clear timer3A timeout flag
  TIMER3_ICR_R = TIMER_ICR_TATOCINT;
  TIMER3_IMR_R |= TIMER_IMR_TATOIM;// 6) arm timeout interrupt
  NVIC_PRI8_R = (NVIC_PRI8_R&0x00FFFFFF)|(1<<29); // 7) Timer3A priority 2, modified
  NVIC_EN1_R &= ~0x00000008;     // 8) disable interrupt 35 in NVIC
  TIMER3_CTL_R |= TIMER_CTL_TAEN;  // 9) enable timer0A
	
  EndCritical(sr);
	return 1;
}

// ******** OS_Time ************
// return the system time 
// Inputs:  none
// Outputs: time in 12.5ns units, 0 to 4294967295
unsigned long OS_Time(void){
	unsigned long time;
	time = TIMER3_TAV_R;  // read from timer3
	return time;
}


// ******** OS_TimeDifference ************
// Calculates difference between two times
// Inputs:  two times measured with OS_Time
// Outputs: time difference in 12.5ns units 
// assume 32-bit Timer reload value is 0xFFFFFFFF
unsigned long OS_TimeDifference(unsigned long start, unsigned long stop){
	unsigned long TDiff;
	if(start > stop){       // counter does not reload
		TDiff = start - stop;
	}else{                  // counter reload
		TDiff = 4294967296 - (stop - start);
	}
	return TDiff;
}


// ******** Start of OS_Fifo functions ************

Sema4Type CurrentSize;   // .value = free FIFO space
Sema4Type FIFOmutex;     // .value = 1 if resource available
unsigned long FIFOdata[64];
unsigned long *PutPt;
unsigned long *GetPt;

// ******** OS_Fifo_Init ************
// Initialize the Fifo to be empty
// Inputs: size
// Outputs: none 
// In Lab 2, you can ignore the size field
// In Lab 3, you should implement the user-defined fifo size
// In Lab 3, you can put whatever restrictions you want on size
//    e.g., 4 to 64 elements
//    e.g., must be a power of 2,4,8,16,32,64,128
void OS_Fifo_Init(unsigned long size){
	CurrentSize.Value = 64;         // 64 free data space
	FIFOmutex.Value = 1;            // FIFO is available
	PutPt = &FIFOdata[0];           // putPt to the first
	GetPt = &FIFOdata[0];           // getPt to the first
}


// ******** OS_Fifo_Put ************
// Enter one data sample into the Fifo
// Called from the background, so no waiting 
// Inputs:  data
// Outputs: true if data is properly saved,
//          false if data not saved, because it was full
// Since this is called by interrupt handlers 
//  this function can not disable or enable interrupts
// on book page 188
int OS_Fifo_Put(unsigned long data){
	unsigned long *nextPutPt;
	nextPutPt = PutPt+1;
	if(nextPutPt == &FIFOdata[64]){
		nextPutPt = &FIFOdata[0];
	}
	if(nextPutPt == GetPt){      // data lost
		return 0;
	}
	else{                        // put data
		*PutPt = data;
		PutPt = nextPutPt;
		OS_Signal(&CurrentSize);
		return 1;
	}
}


// ******** OS_Fifo_Get ************
// Remove one data sample from the Fifo
// Called in foreground, will spin/block if empty
// Inputs:  none
// Outputs: data 
// on book page 188
unsigned long OS_Fifo_Get(void){
	unsigned long data;
	
	OS_Wait(&CurrentSize);
	OS_bWait(&FIFOmutex);
	data = *(GetPt++);
	if(GetPt == &FIFOdata[64]){
		GetPt = &FIFOdata[0];
	}
	OS_bSignal(&FIFOmutex);
	
	return data;
}


// ******** OS_Fifo_Size ************
// Check the status of the Fifo
// Inputs: none
// Outputs: returns the number of elements in the Fifo
//          greater than zero if a call to OS_Fifo_Get will return right away
//          zero or less than zero if the Fifo is empty 
//          zero or less than zero if a call to OS_Fifo_Get will spin or block
long OS_Fifo_Size(void){
	// to be added
	return 1;
}

// ******** End of OS_Fifo functions ************


// ******** Start of OS_MailBox functions ************ 

// ******** OS_MailBox_Init ************ 
// Initialize communication channel 
unsigned long mail; 
Sema4Type DataValid;    // binary sema4
Sema4Type BoxFree;      // binary sema4
  
void OS_MailBox_Init(void){ 
    DataValid.Value = 0; 
    BoxFree.Value = 1; 
} 
  
// ******** OS_MailBox_Send ************ 
// enter mail into the MailBox 
void OS_MailBox_Send(unsigned long data){ 
    mail = data; 
    OS_bSignal(&DataValid); 
    OS_bWait(&BoxFree); 
} 
  
// ******** OS_MailBox_Recv ************ 
// remove mail from the MailBox 
unsigned long OS_MailBox_Recv(void){ 
    unsigned long data; 
    OS_bWait(&DataValid); 
    data = mail; 
    OS_bSignal(&BoxFree); 
    return data; 
} 
  

// Lab 2

